﻿/*
 *   BasicTests.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using BitFox.Evaluator;

namespace TestEvaluator
{
    /// <summary>
    /// Tests that probe the emulator is doing its basic work (checking handling of fox's types)
    /// </summary>
    [TestClass]
    public class BasicTests
    {
        /// <summary>
        /// The evaluator on which to do tests
        /// </summary>
        Evaluator eval = new Evaluator();

        /// <summary>
        /// Empty ctor
        /// </summary>
        public BasicTests()
        {
        }

        /// <summary>
        /// The context under which tests are runned.
        ///</summary>
        public TestContext TestContext { get; set; }

        /// <summary>
        /// Test than integers are properly handled
        /// </summary>
        [TestMethod]
        public void TestInteger()
        {
            Expression exp = eval.Compile("1+2*3");
            int res = (int)exp.Run();
            Assert.AreEqual(7, res);

            Assert.AreEqual(11, (int)eval.Compile("5+6").Run());
            Assert.AreEqual(-1, (int)eval.Compile("5-6").Run());
            Assert.AreEqual(30, (int)eval.Compile("5*6").Run());
            Assert.AreEqual(6, (int)eval.Compile("12/2").Run());
            Assert.AreEqual(1, (int)eval.Compile("6%5").Run());
            Assert.AreEqual(-11, (int)eval.Compile("-(5+6)").Run());

            Assert.IsTrue((bool)eval.Compile("3>2").Run());
            Assert.IsTrue((bool)eval.Compile("3>=2").Run());
            Assert.IsTrue((bool)eval.Compile("3>=3").Run());
            Assert.IsTrue((bool)eval.Compile("2<3").Run());
            Assert.IsTrue((bool)eval.Compile("2<=3").Run());
            Assert.IsTrue((bool)eval.Compile("3<=3").Run());
            Assert.IsTrue((bool)eval.Compile("2=2").Run());
            Assert.IsTrue((bool)eval.Compile("3#2").Run());

            Assert.IsFalse((bool)eval.Compile("2>3").Run());
            Assert.IsFalse((bool)eval.Compile("2>=3").Run());
            Assert.IsFalse((bool)eval.Compile("3<2").Run());
            Assert.IsFalse((bool)eval.Compile("3<=2").Run());
            Assert.IsFalse((bool)eval.Compile("1=2").Run());
            Assert.IsFalse((bool)eval.Compile("2#2").Run());
        }

        /// <summary>
        /// Test than decimal (fox's 'number') are handled as expected.
        /// </summary>
        [TestMethod]
        public void TestDecimal()
        {
            Expression exp = eval.Compile("1+2.232*3");
            decimal res = (decimal)exp.Run();
            Assert.AreEqual(7.696m, res);

            Assert.AreEqual(6.6m, eval.Compile("4.4 + 2.2").Run());
            Assert.AreEqual(3.3m, eval.Compile("4.4 - 1.1").Run());
            Assert.AreEqual(9.68m, eval.Compile("4.4 * 2.2").Run());
            Assert.AreEqual(2m, eval.Compile("4.4 / 2.2").Run());
            Assert.AreEqual(-6.6m, eval.Compile("-(4.4 + 2.2)").Run());

            Assert.IsTrue((bool)eval.Compile("3.3>2.2").Run());
            Assert.IsTrue((bool)eval.Compile("3.3>=2.2").Run());
            Assert.IsTrue((bool)eval.Compile("3.3>=3.3").Run());
            Assert.IsTrue((bool)eval.Compile("2.2<3.3").Run());
            Assert.IsTrue((bool)eval.Compile("2.2<=3.3").Run());
            Assert.IsTrue((bool)eval.Compile("3.3<=3.3").Run());
            Assert.IsTrue((bool)eval.Compile("2.2=2.2").Run());
            Assert.IsTrue((bool)eval.Compile("3.3#2.2").Run());

            Assert.IsFalse((bool)eval.Compile("2.2>3.3").Run());
            Assert.IsFalse((bool)eval.Compile("2.2>=3.3").Run());
            Assert.IsFalse((bool)eval.Compile("3.3<2.2").Run());
            Assert.IsFalse((bool)eval.Compile("3.3<=2.2").Run());
            Assert.IsFalse((bool)eval.Compile("1.1=2.2").Run());
            Assert.IsFalse((bool)eval.Compile("2.2#2.2").Run());
        }

        /// <summary>
        /// Test thatn strings are handled as expected
        /// </summary>
        [TestMethod]
        public void TestStrings()
        {
            Assert.AreEqual("caca de gizmo", eval.Compile("'caca ' + 'de ' + 'gizmo'").Run());
            Assert.AreEqual("cacadegizmo", eval.Compile("'caca ' - '   de  ' - '  gizmo'").Run());

            Assert.IsTrue((bool)eval.Compile("'abc' < 'bbc'").Run());
            Assert.IsTrue((bool)eval.Compile("'abc' <= 'bbc'").Run());
            Assert.IsTrue((bool)eval.Compile("'abc' <= 'abc'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' > 'abc'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' >= 'abc'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' >= 'bbc'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' = 'bbc'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' == 'bbc'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' != 'bac'").Run());
            Assert.IsTrue((bool)eval.Compile("'bbc' != 'bbcaca'").Run());

            Assert.IsFalse((bool)eval.Compile("'abc' > 'bbc'").Run());
            Assert.IsFalse((bool)eval.Compile("'abc' >= 'bbc'").Run());
            Assert.IsFalse((bool)eval.Compile("'bbc' < 'abc'").Run());
            Assert.IsFalse((bool)eval.Compile("'bbc' <= 'abc'").Run());
            Assert.IsFalse((bool)eval.Compile("'bbc' = 'abc'").Run());
            Assert.IsFalse((bool)eval.Compile("'bbc' == 'abc'").Run());
            Assert.IsFalse((bool)eval.Compile("'bbc' != 'bbc'").Run());
        }

        /// <summary>
        /// Test than booleans are handled as expected
        /// </summary>
        [TestMethod]
        public void TestBooleans()
        {
            Assert.AreEqual("caca de gizmo ", eval.Compile("'caca ' + 'de ' + 'gizmo '").Run());
            Assert.AreEqual("cacadegizmo", eval.Compile("'caca ' - '   de  ' - '  gizmo'").Run());

            Assert.IsTrue((bool)eval.Compile(".t.").Run());
            Assert.IsFalse((bool)eval.Compile(".f.").Run());
            Assert.IsTrue((bool)eval.Compile(".t. or .t.").Run());
            Assert.IsTrue((bool)eval.Compile(".t. or .f.").Run());
            Assert.IsTrue((bool)eval.Compile(".f. or .t.").Run());
            Assert.IsFalse((bool)eval.Compile(".f. or .f.").Run());
            Assert.IsTrue((bool)eval.Compile(".t. and .t.").Run());
            Assert.IsFalse((bool)eval.Compile(".t. and .f.").Run());
            Assert.IsFalse((bool)eval.Compile(".f. and .t.").Run());
            Assert.IsFalse((bool)eval.Compile(".f. and .f.").Run());
        }

        /// <summary>
        /// Test than DATETIME are handled as expected
        /// </summary>
        [TestMethod]
        public void TestDateTime()
        {
            Assert.AreEqual(new DateTime(2009, 11, 27), eval.Compile("{^2009/11/27}").Run());

            Assert.IsTrue((bool)eval.Compile("{^2009/10/01} < {^2009/11/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/10/01} <= {^2009/11/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/10/01} <= {^2009/10/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/11/01} > {^2009/10/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/11/01} >= {^2009/10/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/11/01} >= {^2009/11/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/11/01} == {^2009/11/01}").Run());
            Assert.IsTrue((bool)eval.Compile("{^2009/11/01} != {^2009/10/01}").Run());

            Assert.IsFalse((bool)eval.Compile("{^2009/10/01} > {^2009/11/01}").Run());
            Assert.IsFalse((bool)eval.Compile("{^2009/10/01} >= {^2009/11/01}").Run());
            Assert.IsFalse((bool)eval.Compile("{^2009/11/01} < {^2009/10/01}").Run());
            Assert.IsFalse((bool)eval.Compile("{^2009/11/01} <= {^2009/10/01}").Run());
            Assert.IsFalse((bool)eval.Compile("{^2009/11/01} != {^2009/11/01}").Run());
            Assert.IsFalse((bool)eval.Compile("{^2009/11/01} == {^2009/10/01}").Run());
        }

        /// <summary>
        /// Test than operators are correctly priorized
        /// </summary>
        [TestMethod]
        public void TestOperatorsPriority()
        {
            Assert.AreEqual(5, (int)eval.Compile("1+2*6%4").Run());
            Assert.AreEqual(5, (int)eval.Compile("6%4*2+1").Run());
            Assert.AreEqual(10, (int)eval.Compile("3+3*2+1").Run());
            Assert.AreEqual(18, (int)eval.Compile("(3+3)*(2+1)").Run());
        }

        /// <summary>
        /// Test than variables are correctly consumed
        /// </summary>
        [TestMethod]
        public void TestSimpleVariableHandling()
        {
            eval.NameSolvers.Add(SimpleNameSolver);
            variables["VAR1"] = 5;
            variables["N3"] = 7;
            eval.Variables["NUM4"] = -3;        // Test local variables too

            var exp = eval.Compile("(var1 + 2 * n3) * NUM4");
            Assert.AreEqual(-57, (int)exp.Run());
        }

        /// <summary>
        /// Test that builtin functions are invoked (After this, each function has its own test in other class)
        /// </summary>
        [TestMethod]
        public void TestFunctionsCall()
        {
            Assert.AreEqual(1244, (int)eval.Compile("val('1234') + 10").Run());
            Assert.AreEqual("caca", (string)eval.Compile("  alltrim('   caca ')  ").Run());
        }

        /// <summary>
        /// Test than arrays are correctly handled
        /// </summary>
        [TestMethod]
        public void TestSimpleArray()
        {
            eval.Variables["A"] = new int[] { 1, 9, 5 };
            eval.Variables["BARRAY"] = new string[,]
            {
                {"S11", "S12" },
                {"S21", "S22" }
            };

            Assert.AreEqual(10, (int)eval.Compile("A[1] + a[2]").Run());

            eval.Context.ArrayIndexBaseZero = true;
            Assert.AreEqual(10, (int)eval.Compile("A[0] + a[1]").Run());
            Assert.AreEqual(10, (int)eval.Compile("A(0) + a(1)").Run());

            Assert.AreEqual("S12S21 ** S22S11", (string)eval.Compile("BARRAY[0,1] + BARRAY[1,0] + ' ** ' + BARRAY[A[0],1] + BARRAY[0,A[0]-1]").Run());
            Assert.AreEqual("S12S21 ** S22S11", (string)eval.Compile("BARRAY(0,1) + BARRAY(1,0) + ' ** ' + BARRAY(A(0),1) + BARRAY(0,A(0)-1)").Run());
        }

        #region Simple name solver

        /// <summary>
        /// The dictionary that store variables
        /// </summary>
        Dictionary<string, object> variables = new Dictionary<string, object>();

        /// <summary>
        /// The solver used to get variable's values.
        /// </summary>
        /// <param name="req"></param>
        public void SimpleNameSolver(NameRequest req)
        {
            string vname = req.Name.ToUpper().Trim();
            if (req.Operation == NameOperation.Get)
            {
                if (!variables.ContainsKey(vname)) return;
                req.Value = variables[vname];
                req.Resolved = true;
            }
            else
                variables[vname] = req.Value;
        }
        
        #endregion
    }
}
